﻿using System;
using System.Windows.Input;
using Ebt.Mvvm.Behaviors;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;

namespace Ebt.Mvvm
{
    /// <summary>
    /// Allows binding of commands to specific events.
    /// </summary>
    public static class EventBinding
    {
        private static bool Execute(ICommand command, object commandParam)
        {
            if (command == null)
                return false;

            command.Execute(commandParam);

            var eventCommand = command as IEventCommand;
            return eventCommand != null && eventCommand.PreventBubbling;
        }

        private static bool Execute(Func<DependencyObject, ICommand> getCommand, object data, object sender)
        {
            var d = sender as DependencyObject;

            if (d == null)
                return false;

            data = GetIsCommandParameterSet(d) ? GetCommandParameter(d) : data;

            return Execute(getCommand(d), data);
        }

        private static void SubscribeToEvent<TElement>(DependencyObject d, DependencyPropertyChangedEventArgs e, Action<TElement> subscribe, Action<TElement> unsubscribe) where TElement : class
        {
            var element = d as TElement;
            if (element != null)
            {
                if (e.OldValue != null)
                    unsubscribe(element);

                if (e.NewValue != null)
                    subscribe(element);
            }
        }

        #region CommandParameter attached property

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnCommandParameterPropertyChanged));

        /// <summary>
        /// Allows to specify the parameter that will be passed to the <see cref="ICommand.Execute"/> method.
        /// </summary>
        /// <param name="d">The event owner.</param>
        /// <param name="value">The command parameter.</param>
        public static void SetCommandParameter(DependencyObject d, object value)
        {
            d.SetValue(CommandParameterProperty, value);
        }

        public static object GetCommandParameter(DependencyObject d)
        {
            return d.GetValue(CommandParameterProperty);
        }

        private static void OnCommandParameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SetIsCommandParameterSet(d, true);
        }

        #endregion

        #region IsCommandParameterSet attached property

        public static readonly DependencyProperty IsCommandParameterSetProperty =
            DependencyProperty.RegisterAttached("IsCommandParameterSet", typeof(bool), typeof(EventBinding), null);

        public static void SetIsCommandParameterSet(DependencyObject d, bool value)
        {
            d.SetValue(IsCommandParameterSetProperty, value);
        }

        public static bool GetIsCommandParameterSet(DependencyObject d)
        {
            return (bool)d.GetValue(IsCommandParameterSetProperty);
        }

        #endregion

        #region SelectionChanged

        public static readonly DependencyProperty SelectionChangedProperty =
            DependencyProperty.RegisterAttached("SelectionChanged", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnSelectionChangedPropertyChanged));

        public static void SetSelectionChanged(DependencyObject d, ICommand value)
        {
            d.SetValue(SelectionChangedProperty, value);
        }

        public static ICommand GetSelectionChanged(DependencyObject d)
        {
            return (ICommand)d.GetValue(SelectionChangedProperty);
        }

        private static void OnSelectionChangedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<Selector>(d, e, el => el.SelectionChanged += OnSelectionChanged, el => el.SelectionChanged -= OnSelectionChanged);
        }

        static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var selector = (Selector)sender;
            var command = GetSelectionChanged(selector);
            command.Execute(selector.SelectedValue);
        }

        #endregion

        #region Click
        public static readonly DependencyProperty ClickProperty = DependencyProperty.RegisterAttached("Click", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnClickPropertyChanged));

        private static void OnClickPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<ButtonBase>(d, e, el => el.Click += OnClick, el => el.Click -= OnClick);
        }

        static void OnClick(object sender, RoutedEventArgs e)
        {
            var listView = (ButtonBase)sender;
            var command = GetClick(listView);
            command.Execute(e);
        }

        public static void SetClick(DependencyObject element, ICommand value)
        {
            element.SetValue(ClickProperty, value);
        }

        public static ICommand GetClick(DependencyObject element)
        {
            return (ICommand)element.GetValue(ClickProperty);
        }
        #endregion

        #region ItemClick

        /// <summary>
        /// The <see cref="ListViewBase.ItemClick"/> event command. Command parameter is the clicked item.
        /// </summary>
        public static readonly DependencyProperty ItemClickProperty = DependencyProperty.RegisterAttached("ItemClick", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnItemClickPropertyChanged));

        private static void OnItemClickPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<ListViewBase>(d, e, el => el.ItemClick += OnItemClick, el => el.ItemClick -= OnItemClick);
        }

        static void OnItemClick(object sender, ItemClickEventArgs e)
        {
            var listView = (ListViewBase)sender;
            var command = GetItemClick(listView);
            command.Execute(e.ClickedItem);
        }

        /// <summary>
        /// The <see cref="ListViewBase.ItemClick"/> event command. Command parameter is the clicked item.
        /// </summary>
        public static void SetItemClick(DependencyObject element, ICommand value)
        {
            element.SetValue(ItemClickProperty, value);
        }

        /// <summary>
        /// The <see cref="ListViewBase.ItemClick"/> event command. Command parameter is the clicked item.
        /// </summary>
        public static ICommand GetItemClick(DependencyObject element)
        {
            return (ICommand)element.GetValue(ItemClickProperty);
        }

        #endregion

        #region KeyDown

        public static readonly DependencyProperty KeyDownProperty = DependencyProperty.RegisterAttached("KeyDown", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnKeyDownPropertyChanged));

        private static void OnKeyDownPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.KeyDown += OnKeyDown, el => el.KeyDown -= OnKeyDown);
        }

        static void OnKeyDown(object sender, KeyRoutedEventArgs e)
        {
            e.Handled |= Execute(GetKeyDown, e.Key, sender);
        }

        public static void SetKeyDown(DependencyObject obj, ICommand value)
        {
            obj.SetValue(KeyDownProperty, value);
        }

        public static ICommand GetKeyDown(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(KeyDownProperty);
        }

        #endregion

        #region KeyUp

        public static readonly DependencyProperty KeyUpProperty = DependencyProperty.RegisterAttached("KeyUp", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnKeyUpPropertyChanged));

        private static void OnKeyUpPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.KeyUp += OnKeyUp, el => el.KeyUp -= OnKeyUp);
        }

        static void OnKeyUp(object sender, KeyRoutedEventArgs e)
        {
            e.Handled |= Execute(GetKeyUp, e.Key, sender);
        }

        public static void SetKeyUp(DependencyObject d, ICommand value)
        {
            d.SetValue(KeyUpProperty, value);
        }

        public static ICommand GetKeyUp(DependencyObject d)
        {
            return (ICommand)d.GetValue(KeyUpProperty);
        }

        #endregion

        #region PointerReleased

        public static readonly DependencyProperty PointerReleasedProperty = DependencyProperty.RegisterAttached("PointerReleased", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnPointerReleasedPropertyChanged));

        private static void OnPointerReleasedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.PointerReleased += OnPointerReleased, el => el.PointerReleased -= OnPointerReleased);
        }

        static void OnPointerReleased(object sender, PointerRoutedEventArgs e)
        {
            var location = e.GetCurrentPoint((FrameworkElement)sender);
            e.Handled |= Execute(GetPointerReleased, location, sender);
        }

        public static void SetPointerReleased(DependencyObject d, ICommand value)
        {
            d.SetValue(PointerReleasedProperty, value);
        }

        public static ICommand GetPointerReleased(DependencyObject d)
        {
            return (ICommand)d.GetValue(PointerReleasedProperty);
        }

        #endregion

        #region PointerPressed

        public static readonly DependencyProperty PointerPressedProperty =
            DependencyProperty.RegisterAttached("PointerPressed", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnPointerPressedPropertyChanged));

        public static void SetPointerPressed(DependencyObject d, ICommand value)
        {
            d.SetValue(PointerPressedProperty, value);
        }

        public static ICommand GetPointerPressed(DependencyObject d)
        {
            return (ICommand)d.GetValue(PointerPressedProperty);
        }

        private static void OnPointerPressedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var element = d as FrameworkElement;
            if (element != null)
            {
                SubscribeToEvent<FrameworkElement>(d, e, el => el.PointerPressed += OnPointerPressed, el => el.PointerPressed -= OnPointerPressed);
            }
        }

        static void OnPointerPressed(object sender, PointerRoutedEventArgs e)
        {
            var location = e.GetCurrentPoint((FrameworkElement)sender);
            e.Handled |= Execute(GetPointerPressed, location, sender);
        }

        #endregion

        #region PointerMoved

        public static readonly DependencyProperty PointerMovedProperty =
            DependencyProperty.RegisterAttached("PointerMoved", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnPointerMovedPropertyChanged));

        public static void SetPointerMoved(DependencyObject d, ICommand value)
        {
            d.SetValue(PointerMovedProperty, value);
        }

        public static ICommand GetPointerMoved(DependencyObject d)
        {
            return (ICommand)d.GetValue(PointerMovedProperty);
        }

        private static void OnPointerMovedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.PointerMoved += OnPointerMoved, el => el.PointerMoved -= OnPointerMoved);
        }

        static void OnPointerMoved(object sender, PointerRoutedEventArgs e)
        {
            var location = e.GetCurrentPoint((FrameworkElement)sender);
            e.Handled |= Execute(GetPointerMoved, location, sender);
        }

        #endregion

        #region PointerCanceled

        public static readonly DependencyProperty PointerCanceledProperty =
            DependencyProperty.RegisterAttached("PointerCanceled", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnPointerCanceledPropertyChanged));

        public static void SetPointerCanceled(DependencyObject d, ICommand value)
        {
            d.SetValue(PointerCanceledProperty, value);
        }

        public static ICommand GetPointerCanceled(DependencyObject d)
        {
            return (ICommand)d.GetValue(PointerCanceledProperty);
        }

        private static void OnPointerCanceledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.PointerCanceled += OnPointerCanceled, el => el.PointerCanceled -= OnPointerCanceled);
        }

        static void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
        {
            var location = e.GetCurrentPoint((FrameworkElement)sender);
            e.Handled |= Execute(GetPointerCanceled, location, sender);
        }

        #endregion

        #region PointerEntered

        public static readonly DependencyProperty PointerEnteredProperty =
            DependencyProperty.RegisterAttached("PointerEntered", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnPointerEnteredPropertyChanged));

        public static void SetPointerEntered(DependencyObject d, ICommand value)
        {
            d.SetValue(PointerEnteredProperty, value);
        }

        public static ICommand GetPointerEntered(DependencyObject d)
        {
            return (ICommand)d.GetValue(PointerEnteredProperty);
        }

        private static void OnPointerEnteredPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.PointerEntered += OnPointerEntered, el => el.PointerEntered -= OnPointerEntered);
        }

        static void OnPointerEntered(object sender, PointerRoutedEventArgs e)
        {
            var location = e.GetCurrentPoint((FrameworkElement)sender);
            e.Handled |= Execute(GetPointerEntered, location, sender);
        }

        #endregion

        #region PointerExited

        public static readonly DependencyProperty PointerExitedProperty =
            DependencyProperty.RegisterAttached("PointerExited", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnPointerExitedPropertyChanged));

        public static void SetPointerExited(DependencyObject d, ICommand value)
        {
            d.SetValue(PointerExitedProperty, value);
        }

        public static ICommand GetPointerExited(DependencyObject d)
        {
            return (ICommand)d.GetValue(PointerExitedProperty);
        }

        private static void OnPointerExitedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.PointerExited += OnPointerExited, el => el.PointerExited -= OnPointerExited);
        }

        static void OnPointerExited(object sender, PointerRoutedEventArgs e)
        {
            var location = e.GetCurrentPoint((FrameworkElement)sender);
            e.Handled |= Execute(GetPointerExited, location, sender);
        }

        #endregion

        #region PointerCaptureLost

        public static readonly DependencyProperty PointerCaptureLostProperty =
            DependencyProperty.RegisterAttached("PointerCaptureLost", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnPointerCaptureLostPropertyChanged));

        public static void SetPointerCaptureLost(DependencyObject d, ICommand value)
        {
            d.SetValue(PointerCaptureLostProperty, value);
        }

        public static ICommand GetPointerCaptureLost(DependencyObject d)
        {
            return (ICommand)d.GetValue(PointerCaptureLostProperty);
        }

        private static void OnPointerCaptureLostPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.PointerCaptureLost += OnPointerCaptureLost, el => el.PointerCaptureLost -= OnPointerCaptureLost);
        }

        static void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
        {
            var location = e.GetCurrentPoint((FrameworkElement)sender);
            e.Handled |= Execute(GetPointerCaptureLost, location, sender);
        }

        #endregion

        #region Tapped

        public static readonly DependencyProperty TappedProperty =
            DependencyProperty.RegisterAttached("Tapped", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnTappedPropertyChanged));

        public static void SetTapped(DependencyObject d, ICommand value)
        {
            d.SetValue(TappedProperty, value);
        }

        public static ICommand GetTapped(DependencyObject d)
        {
            return (ICommand)d.GetValue(TappedProperty);
        }

        private static void OnTappedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.Tapped += OnTapped, el => el.Tapped -= OnTapped);
        }

        static void OnTapped(object sender, TappedRoutedEventArgs e)
        {
            var location = e.GetPosition((FrameworkElement)sender);
            e.Handled |= Execute(GetTapped, location, sender);
        }

        #endregion

        #region RightTapped

        public static readonly DependencyProperty RightTappedProperty =
            DependencyProperty.RegisterAttached("RightTapped", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnRightTappedPropertyChanged));

        public static void SetRightTapped(DependencyObject d, ICommand value)
        {
            d.SetValue(RightTappedProperty, value);
        }

        public static ICommand GetRightTapped(DependencyObject d)
        {
            return (ICommand)d.GetValue(RightTappedProperty);
        }

        private static void OnRightTappedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.RightTapped += OnRightTapped, el => el.RightTapped -= OnRightTapped);
        }

        static void OnRightTapped(object sender, RightTappedRoutedEventArgs e)
        {
            var location = e.GetPosition((FrameworkElement)sender);
            e.Handled |= Execute(GetRightTapped, location, sender);
        }

        #endregion

        #region DoubleTapped attached property

        public static readonly DependencyProperty DoubleTappedProperty =
            DependencyProperty.RegisterAttached("DoubleTapped", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnDoubleTappedPropertyChanged));

        public static void SetDoubleTapped(DependencyObject d, ICommand value)
        {
            d.SetValue(DoubleTappedProperty, value);
        }

        public static ICommand GetDoubleTapped(DependencyObject d)
        {
            return (ICommand)d.GetValue(DoubleTappedProperty);
        }

        private static void OnDoubleTappedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.DoubleTapped += OnDoubleTapped, el => el.DoubleTapped -= OnDoubleTapped);
        }

        private static void OnDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
        {
            var location = e.GetPosition((FrameworkElement)sender);
            e.Handled |= e.Handled |= Execute(GetDoubleTapped, location, sender);
        }

        #endregion

        #region Holding attached property

        public static readonly DependencyProperty HoldingProperty =
            DependencyProperty.RegisterAttached("Holding", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnHoldingPropertyChanged));

        public static void SetHolding(DependencyObject d, ICommand value)
        {
            d.SetValue(HoldingProperty, value);
        }

        public static ICommand GetHolding(DependencyObject d)
        {
            return (ICommand)d.GetValue(HoldingProperty);
        }

        private static void OnHoldingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.Holding += OnHolding, el => el.Holding -= OnHolding);
        }

        private static void OnHolding(object sender, HoldingRoutedEventArgs e)
        {
            var location = e.GetPosition((FrameworkElement)sender);
            e.Handled |= Execute(
                GetHolding,
                new HoldingCommandParam { Point = location, State = e.HoldingState },
                sender);
        }

        #endregion

        #region PointerWheelChanged

        public static readonly DependencyProperty PointerWheelChangedProperty =
            DependencyProperty.RegisterAttached("PointerWheelChanged", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnPointerWheelChangedPropertyChanged));

        public static void SetPointerWheelChanged(DependencyObject d, ICommand value)
        {
            d.SetValue(PointerWheelChangedProperty, value);
        }

        public static ICommand GetPointerWheelChanged(DependencyObject d)
        {
            return (ICommand)d.GetValue(PointerWheelChangedProperty);
        }

        private static void OnPointerWheelChangedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.PointerWheelChanged += OnPointerWheelChanged, el => el.PointerWheelChanged -= OnPointerWheelChanged);
        }

        static void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e)
        {
            var location = e.GetCurrentPoint((FrameworkElement)sender);
            e.Handled |= Execute(GetRightTapped, location, sender);
        }

        #endregion

        #region ManipulationStarting

        public static readonly DependencyProperty ManipulationStartingProperty =
            DependencyProperty.RegisterAttached("ManipulationStarting", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnManipulationStartingPropertyChanged));

        public static void SetManipulationStarting(DependencyObject d, ICommand value)
        {
            d.SetValue(ManipulationStartingProperty, value);
        }

        public static ICommand GetManipulationStarting(DependencyObject d)
        {
            return (ICommand)d.GetValue(ManipulationStartingProperty);
        }

        private static void OnManipulationStartingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.ManipulationStarting += OnManipulationStarting, el => el.ManipulationStarting -= OnManipulationStarting);
        }

        static void OnManipulationStarting(object sender, ManipulationStartingRoutedEventArgs e)
        {
            e.Handled |= Execute(GetManipulationStarting, e, sender);
        }

        #endregion

        #region ManipulationStarted

        public static readonly DependencyProperty ManipulationStartedProperty =
            DependencyProperty.RegisterAttached("ManipulationStarted", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnManipulationStartedPropertyChanged));

        public static void SetManipulationStarted(DependencyObject d, ICommand value)
        {
            d.SetValue(ManipulationStartedProperty, value);
        }

        public static ICommand GetManipulationStarted(DependencyObject d)
        {
            return (ICommand)d.GetValue(ManipulationStartedProperty);
        }

        private static void OnManipulationStartedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.ManipulationStarted += OnManipulationStarted, el => el.ManipulationStarted -= OnManipulationStarted);
        }

        static void OnManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
        {
            e.Handled |= Execute(GetManipulationStarted, e, sender);
        }

        #endregion

        #region ManipulationDelta

        public static readonly DependencyProperty ManipulationDeltaProperty =
            DependencyProperty.RegisterAttached("ManipulationDelta", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnManipulationDeltaPropertyChanged));

        public static void SetManipulationDelta(DependencyObject d, ICommand value)
        {
            d.SetValue(ManipulationDeltaProperty, value);
        }

        public static ICommand GetManipulationDelta(DependencyObject d)
        {
            return (ICommand)d.GetValue(ManipulationDeltaProperty);
        }

        private static void OnManipulationDeltaPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.ManipulationDelta += OnManipulationDelta, el => el.ManipulationDelta -= OnManipulationDelta);
        }

        static void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            e.Handled |= Execute(GetManipulationDelta, e, sender);
        }

        #endregion

        #region ManipulationInertiaStarting

        public static readonly DependencyProperty ManipulationInertiaStartingProperty =
            DependencyProperty.RegisterAttached("ManipulationInertiaStarting", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnManipulationInertiaStartingPropertyChanged));

        public static void SetManipulationInertiaStarting(DependencyObject d, ICommand value)
        {
            d.SetValue(ManipulationInertiaStartingProperty, value);
        }

        public static ICommand GetManipulationInertiaStarting(DependencyObject d)
        {
            return (ICommand)d.GetValue(ManipulationInertiaStartingProperty);
        }

        private static void OnManipulationInertiaStartingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.ManipulationInertiaStarting += OnManipulationInertiaStarting, el => el.ManipulationInertiaStarting -= OnManipulationInertiaStarting);
        }

        static void OnManipulationInertiaStarting(object sender, ManipulationInertiaStartingRoutedEventArgs e)
        {
            e.Handled |= Execute(GetManipulationInertiaStarting, e, sender);
        }

        #endregion

        #region ManipulationCompleted

        public static readonly DependencyProperty ManipulationCompletedProperty =
            DependencyProperty.RegisterAttached("ManipulationCompleted", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnManipulationCompletedPropertyChanged));

        public static void SetManipulationCompleted(DependencyObject d, ICommand value)
        {
            d.SetValue(ManipulationCompletedProperty, value);
        }

        public static ICommand GetManipulationCompleted(DependencyObject d)
        {
            return (ICommand)d.GetValue(ManipulationCompletedProperty);
        }

        private static void OnManipulationCompletedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.ManipulationCompleted += OnManipulationCompleted, el => el.ManipulationCompleted -= OnManipulationCompleted);
        }

        static void OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
        {
            e.Handled |= Execute(GetManipulationCompleted, e, sender);
        }

        #endregion

        #region LostFocus

        public static readonly DependencyProperty LostFocusProperty =
            DependencyProperty.RegisterAttached("LostFocus", typeof(ICommand), typeof(EventBinding), new PropertyMetadata(DependencyProperty.UnsetValue, OnLostFocusPropertyChanged));

        public static void SetLostFocus(DependencyObject d, ICommand value)
        {
            d.SetValue(LostFocusProperty, value);
        }

        public static ICommand GetLostFocus(DependencyObject d)
        {
            return (ICommand)d.GetValue(LostFocusProperty);
        }

        private static void OnLostFocusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.LostFocus += OnLostFocus, el => el.LostFocus -= OnLostFocus);
        }

        private static void OnLostFocus(object sender, RoutedEventArgs e)
        {
            var element = (UIElement)sender;
            var command = GetLostFocus(element);
            command.Execute(null);
        }

        #endregion

        #region Unloaded

        /// <summary>
        /// The <see cref="FrameworkElement.Unloaded"/> event command.
        /// </summary>
        public static readonly DependencyProperty UnloadedProperty = DependencyProperty.RegisterAttached("Unloaded", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnUnloadedPropertyChanged));

        private static void OnUnloadedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.Unloaded += OnUnloaded, el => el.Unloaded -= OnUnloaded);
        }

        static void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
        {
            var listView = (FrameworkElement)sender;
            var command = GetUnloaded(listView);
            command.Execute(null);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.Unloaded"/> event command.
        /// </summary>
        public static void SetUnloaded(DependencyObject element, ICommand value)
        {
            element.SetValue(UnloadedProperty, value);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.Unloaded"/> event command.
        /// </summary>
        public static ICommand GetUnloaded(DependencyObject element)
        {
            return (ICommand)element.GetValue(UnloadedProperty);
        }

        #endregion

        #region Loaded

        /// <summary>
        /// The <see cref="FrameworkElement.Loaded"/> event command.
        /// </summary>
        public static readonly DependencyProperty LoadedProperty = DependencyProperty.RegisterAttached("Loaded", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnLoadedPropertyChanged));

        private static void OnLoadedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.Loaded += OnLoaded, el => el.Loaded -= OnLoaded);
        }

        static void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            var listView = (FrameworkElement)sender;
            var command = GetLoaded(listView);
            command.Execute(null);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.Loaded"/> event command.
        /// </summary>
        public static void SetLoaded(DependencyObject element, ICommand value)
        {
            element.SetValue(LoadedProperty, value);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.Loaded"/> event command.
        /// </summary>
        public static ICommand GetLoaded(DependencyObject element)
        {
            return (ICommand)element.GetValue(LoadedProperty);
        }

        #endregion

        #region Drop

        /// <summary>
        /// The <see cref="FrameworkElement.Drop"/> event command. Command parameter is <see cref="DragCommandParam"/>.
        /// </summary>
        public static readonly DependencyProperty DropProperty = DependencyProperty.RegisterAttached("Drop", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnDropPropertyChanged));

        private static void OnDropPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.Drop += OnDrop, el => el.Drop -= OnDrop);
        }

        static void OnDrop(object sender, DragEventArgs e)
        {
            var location = e.GetPosition((FrameworkElement)sender);
            var param = new DragCommandParam { Data = e.Data, Point = location };
            e.Handled |= Execute(GetDrop, param, sender);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.Drop"/> event command.
        /// </summary>
        public static void SetDrop(DependencyObject element, ICommand value)
        {
            element.SetValue(DropProperty, value);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.Drop"/> event command.
        /// </summary>
        public static ICommand GetDrop(DependencyObject element)
        {
            return (ICommand)element.GetValue(DropProperty);
        }

        #endregion

        #region DragEnter

        /// <summary>
        /// The <see cref="FrameworkElement.DragEnter"/> event command. Command parameter is <see cref="DragCommandParam"/>.
        /// </summary>
        public static readonly DependencyProperty DragEnterProperty = DependencyProperty.RegisterAttached("DragEnter", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnDragEnterPropertyChanged));

        private static void OnDragEnterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.DragEnter += OnDragEnter, el => el.DragEnter -= OnDragEnter);
        }

        static void OnDragEnter(object sender, DragEventArgs e)
        {
            var location = e.GetPosition((FrameworkElement)sender);
            var param = new DragCommandParam { Data = e.Data, Point = location };
            e.Handled |= Execute(GetDragEnter, param, sender);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.DragEnter"/> event command.
        /// </summary>
        public static void SetDragEnter(DependencyObject element, ICommand value)
        {
            element.SetValue(DragEnterProperty, value);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.DragEnter"/> event command.
        /// </summary>
        public static ICommand GetDragEnter(DependencyObject element)
        {
            return (ICommand)element.GetValue(DragEnterProperty);
        }

        #endregion

        #region DragOver

        /// <summary>
        /// The <see cref="FrameworkElement.DragOver"/> event command. Command parameter is <see cref="DragCommandParam"/>.
        /// </summary>
        public static readonly DependencyProperty DragOverProperty = DependencyProperty.RegisterAttached("DragOver", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnDragOverPropertyChanged));

        private static void OnDragOverPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.DragOver += OnDragOver, el => el.DragOver -= OnDragOver);
        }

        static void OnDragOver(object sender, DragEventArgs e)
        {
            var location = e.GetPosition((FrameworkElement)sender);
            var param = new DragCommandParam { Data = e.Data, Point = location };
            e.Handled |= Execute(GetDragOver, param, sender);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.DragOver"/> event command.
        /// </summary>
        public static void SetDragOver(DependencyObject element, ICommand value)
        {
            element.SetValue(DragOverProperty, value);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.DragOver"/> event command.
        /// </summary>
        public static ICommand GetDragOver(DependencyObject element)
        {
            return (ICommand)element.GetValue(DragOverProperty);
        }

        #endregion

        #region DragLeave

        /// <summary>
        /// The <see cref="FrameworkElement.DragLeave"/> event command. Command parameter is <see cref="DragCommandParam"/>.
        /// </summary>
        public static readonly DependencyProperty DragLeaveProperty = DependencyProperty.RegisterAttached("DragLeave", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnDragLeavePropertyChanged));

        private static void OnDragLeavePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<FrameworkElement>(d, e, el => el.DragLeave += OnDragLeave, el => el.DragLeave -= OnDragLeave);
        }

        static void OnDragLeave(object sender, DragEventArgs e)
        {
            var location = e.GetPosition((FrameworkElement)sender);
            var param = new DragCommandParam { Data = e.Data, Point = location };
            e.Handled |= Execute(GetDragLeave, param, sender);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.DragLeave"/> event command.
        /// </summary>
        public static void SetDragLeave(DependencyObject element, ICommand value)
        {
            element.SetValue(DragLeaveProperty, value);
        }

        /// <summary>
        /// The <see cref="FrameworkElement.DragLeave"/> event command.
        /// </summary>
        public static ICommand GetDragLeave(DependencyObject element)
        {
            return (ICommand)element.GetValue(DragLeaveProperty);
        }

        #endregion

        #region DragItemsStarting

        /// <summary>
        /// The <see cref="ListViewBase.DragItemsStarting"/> event command. Command parameter is <see cref="DragItemsStartingEventArgs"/>.
        /// </summary>
        public static readonly DependencyProperty DragItemsStartingProperty = DependencyProperty.RegisterAttached("DragItemsStarting", typeof(ICommand), typeof(EventBinding),
            new PropertyMetadata(DependencyProperty.UnsetValue, OnDragItemsStartingPropertyChanged));

        private static void OnDragItemsStartingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SubscribeToEvent<ListViewBase>(d, e, el => el.DragItemsStarting += OnDragItemsStarting, el => el.DragItemsStarting -= OnDragItemsStarting);
        }

        static void OnDragItemsStarting(object sender, DragItemsStartingEventArgs e)
        {
            var listView = (ListViewBase)sender;
            var command = GetDragItemsStarting(listView);
            command.Execute(e);
        }

        /// <summary>
        /// The <see cref="ListViewBase.DragItemsStarting"/> event command. Command parameter is the clicked item.
        /// </summary>
        public static void SetDragItemsStarting(DependencyObject element, ICommand value)
        {
            element.SetValue(DragItemsStartingProperty, value);
        }

        /// <summary>
        /// The <see cref="ListViewBase.DragItemsStarting"/> event command. Command parameter is the clicked item.
        /// </summary>
        public static ICommand GetDragItemsStarting(DependencyObject element)
        {
            return (ICommand)element.GetValue(DragItemsStartingProperty);
        }

        #endregion
    }
}
